/*
 * Open Source Physics software is free software as described near the bottom of this code file.
 *
 * For additional information and documentation on Open Source Physics please see:
 * <http://www.opensourcephysics.org/>
 */

package org.opensourcephysics.tools;
import java.io.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.event.*;
import java.beans.*;
import javax.swing.*;
import org.opensourcephysics.controls.*;
import org.opensourcephysics.display.OSPRuntime;
import org.opensourcephysics.display.TextFrame;
import org.opensourcephysics.display.Hidable;

/**
 * This provides a GUI for creating and editing string resources associated
 * with a class. Resources are stored in properties files
 * with the same name and located in the same folder as the class.
 *
 * @author Douglas Brown
 * @version 1.0
 */
public class TranslatorTool extends JFrame implements Tool, Hidable, Translator {

   // instance fields
   private Dimension dim = new Dimension(320, 240);
   private Map defaultProps = new HashMap();                                                                // class to default names/values
   private Map classes = new HashMap();                                                                     // class to language map
   private Map associates = new HashMap();                                                                  // object to class
   private Set changed = new HashSet();                                                                     // contains maps with unsaved changes
   private Locale locale = Locale.getDefault();
   private Set searched = new HashSet();                                                                    // classes searched for translations
   private String helpURL = "http://www.opensourcephysics.org/online_help/tools/translator_tool_help.html"; //$NON-NLS-1$
   private boolean keepHidden = false;
   private XMLControl control = new XMLControlElement();
   private XMLTable table;
   private Class classType;
   private JPanel contentPane = new JPanel(new BorderLayout());
   private String fileExtension;
   private JLabel descriptionLabel;
   private JComboBox localeDropDown;
   private Icon saveIcon;
   private JButton saveButton;
   private JButton closeButton;
   private JButton helpButton;

   /**
    * The singleton shared translator tool.
    */
   private static final TranslatorTool TOOL = new TranslatorTool();

   /**
    * Gets the shared TranslatorTool.
    *
    * @return the shared TranslatorTool
    */
   public static TranslatorTool getTool() {
      return TOOL;
   }

   /**
    * Shows the frame on the screen if the keep hidden flag is false.
    *
    * @deprecated
    */
   public void show() {
      if(!keepHidden) {
         super.show();
      }
   }

   /**
    * Disposes all resources.
    */
   public void dispose() {
      keepHidden = true;
      super.dispose();
   }

   /**
    * Shows or hides this component depending on the value of parameter
    * <code>b</code> and the <code>keepHidden</code> flag.
    *
    * OSP Applets often keep windows hidden.
    *
    * @param b
    */
   public void setVisible(boolean b) {
      if(!keepHidden) {
         super.setVisible(b);
      }
   }

   /**
    * Sets the keepHidden flag.
    *
    * @param _keepHidden
    */
   public void setKeepHidden(boolean _keepHidden) {
      keepHidden = _keepHidden;
      if(keepHidden) {
         super.setVisible(false);
      }
   }

   /**
    * Reads the keepHidden flag.
    *
    */
   public boolean isKeepHidden() {
      return keepHidden;
   }

   /**
    * Private constructor.
    */
   private TranslatorTool() {
      if(OSPRuntime.appletMode) {
         keepHidden = true;
      }
      String name = "TranslatorTool"; //$NON-NLS-1$
      setName(name);
      createGUI();
      refreshGUI();
      setLocale(ToolsRes.locale);
      Toolbox.addTool(name, this);
   }

   /**
    * Sends a job to this tool and specifies a tool to reply to.
    *
    * @param job the Job
    * @param replyTo the tool to notify when the job is complete (may be null)
    * @throws RemoteException
    */
   public void send(Job job, Tool replyTo) {

   /** not implemented */
   }

   /**
    * Sets the locale.
    *
    * @param locale the locale
    */
   public void setLocale(Locale locale) {
      if(locale==this.locale) {
         return;
      }
      this.locale = locale;
      showProperties(classType);
      // add new item if locale is not in dropdown
      LocaleItem item = null;
      if(localeDropDown!=null) {
         // look for existing item
         for(int i = 0; i<localeDropDown.getItemCount(); i++) {
            item = (LocaleItem) localeDropDown.getItemAt(i);
            if(item.loc.getLanguage().equals(locale.getLanguage())) {
               break;
            }
            item = null;
         }
         // if not found, create and add new item
         if(item==null) {
            item = new LocaleItem(locale);
            addDropDownItem(item);
         }
         localeDropDown.setSelectedItem(item);
         // enable/disable save button
         Map properties = getProperties(classType, locale);
         saveButton.setEnabled(changed.contains(properties));
         // refresh objects associated with current class
         refreshAssociates(classType);
      }
   }

   /**
    * Associates an object with a class for property lookup purposes.
    *
    * @param obj the object needing translations
    * @param type the class
    */
   public synchronized void associate(Object obj, Class type) {
      if(obj==null) {
         return;
      }
      associates.put(obj, type);
   }

   /**
    * Shows the properties for the specified class.
    *
    * @param type the class
    */
   public void showProperties(Class type) {
      if(type==null) {
         return;
      }
      classType = type;
      // clear current values
      control.clearValues();
      // set file extension (language only)
      fileExtension = ""; //$NON-NLS-1$
      String addon = locale.getLanguage();
      if(!addon.equals("")) {        //$NON-NLS-1$
         fileExtension += "_"+addon; //$NON-NLS-1$
      }
      fileExtension += ".properties"; //$NON-NLS-1$
      // initialize control with default values
      Collection names = control.getPropertyNames();
      Iterator it = names.iterator();
      while(it.hasNext()) {
         String next = (String) it.next();
         control.setValue(next, next);
      }
      // set control values per current properties map
      Map properties = getProperties(type, locale);
      it = properties.keySet().iterator();
      while(it.hasNext()) {
         String key = (String) it.next();
         control.setValue(key, properties.get(key));
      }
      // compare with defaults and flag unused properties
      Set keys = getDefaults(type).keySet();
      it = properties.keySet().iterator();
      while(it.hasNext()) {
         String key = (String) it.next();
         if(!keys.contains(key)) {
            table.setBackgroundColor(key, Color.PINK);
         }
      }
      table.refresh();
      refreshGUI();
   }

   /**
    * Gets the localized value of a property for the specified class.
    * If no localized value is found, the key is returned.
    *
    * @param type the class requesting the localized value
    * @param key the string to localize
    * @return the localized string
    */
   public String getProperty(Class type, String key) {
      return getProperty(type, key, key, ToolsRes.locale);
   }

   /**
    * Gets the localized value of a property for the specified class.
    * If no localized value is found, the defaultValue is returned.
    *
    * @param type the class requesting the localized value
    * @param key the string to localize
    * @param defaultValue the default if no localized value found
    * @return the localized string
    */
   public String getProperty(Class type, String key, String defaultValue) {
      return getProperty(type, key, defaultValue, ToolsRes.locale);
   }

   /**
    * Gets the localized value of a property for the specified object.
    * The object must first be associated with a class.
    * If no localized value is found, the key is returned.
    *
    * @param obj the object requesting the localized value
    * @param key the string to localize
    * @return the localized string
    */
   public String getProperty(Object obj, String key) {
      return getProperty(obj, key, key);
   }

   /**
    * Gets the localized value of a property for the specified object.
    * The object must first be associated with a class.
    * If no localized value is found, the defaultValue is returned.
    *
    * @param obj the object requesting the localized value
    * @param key the string to localize
    * @param defaultValue the default if no localized value found
    * @return the localized string
    */
   public String getProperty(Object obj, String key, String defaultValue) {
      if(obj==null) {
         return(defaultValue==null) ? key : defaultValue;
      }
      Class type = (Class) associates.get(obj);
      return getProperty(type, key, defaultValue, ToolsRes.locale);
   }

   /**
    * Removes a property from those defined for the specified class.
    *
    * @param type the class
    * @param key the property to remove
    */
   public void removeProperty(Class type, String key) {
      if(type==null) {
         return;
      }
      // remove key from all properties maps for class type
      getDefaults(type).remove(key);
      Map locales = (Map) classes.get(type);
      if(locales!=null) {
         Iterator it = locales.keySet().iterator();
         while(it.hasNext()) {
            Map properties = (Map) locales.get(it.next());
            properties.remove(key);
            flagChange(properties);
         }
      }
      TOOL.showProperties(TOOL.classType);
      refreshAssociates(TOOL.classType);
   }

   /**
    * Removes a property from those defined for the specified object.
    * The object must first be associated with a class.
    *
    * @param obj the object
    * @param key the property to remove
    */
   public void removeProperty(Object obj, String key) {
      Class type = (Class) associates.get(obj);
      removeProperty(type, key);
   }

   /**
    * Adds a property to those defined for the specified class.
    *
    * @param type the class
    * @param key the property to add
    * @param defaultValue the default value
    */
   public void addProperty(Class type, String key, String defaultValue) {
      if((type==null)||(key==null)) {
         return;
      }
      if(defaultValue==null) {
         defaultValue = key;
      }
      // add key-value to all properties maps for class type
      getDefaults(type).put(key, defaultValue);
      Map properties = getProperties(type, locale);
      if(properties.get(key)==null) {
         properties.put(key, defaultValue);
         flagChange(properties);
      }
      Map locales = (Map) classes.get(type);
      if(locales!=null) {
         Iterator it = locales.keySet().iterator();
         while(it.hasNext()) {
            properties = (Map) locales.get(it.next());
            if(properties.get(key)==null) {
               properties.put(key, defaultValue);
               flagChange(properties);
            }
         }
      }
      TOOL.showProperties(TOOL.classType);
      // refresh objects associated with current class
      refreshAssociates(TOOL.classType);
   }

   /**
    * Gets objects associated with the specified class.
    */
   public Collection getAssociates(Class type) {
      Collection c = new ArrayList();
      Iterator it = associates.keySet().iterator();
      while(it.hasNext()) {
         Object obj = it.next();
         if(associates.get(obj).equals(type)) {
            c.add(obj);
         }
      }
      return c;
   }

   /**
    * Returns Locales for which translations exist for the specified class.
    */
   public Locale[] getTranslatedLocales(Class type) {
      if(!searched.contains(type)) {
         // search for and load all saved locales
         synchronized(searched) {
            searched.add(type);
         }
         Map locales = (Map) classes.get(type);
         if(locales==null) {
            locales = new HashMap();
            synchronized(classes) {
               classes.put(type, locales);
            }
         }
         if(OSPRuntime.applet==null) {                                                //  search for languages if NOT an applet
            Set langs = locales.keySet();                                             // loaded language codes
            String path = getPath(type);
            Resource res = null;
            String[] languages = Locale.getISOLanguages();                            // all standard language codes
            for(int i = 0; i<languages.length; i++) {
               if(langs.contains(languages[i])) {
                  continue;
               }
               res = ResourceLoader.getResource(path+"_"+languages[i]+".properties"); //$NON-NLS-1$ //$NON-NLS-2$
               if(res!=null) {
                  Map properties = new TreeMap();
                  locales.put(languages[i], properties);
                  // fill properties from resource
                  OSPLog.finer(res.getAbsolutePath());
                  readProperties(res.openReader(), properties);
               }
            }
         }
      }
      Set languages = new TreeSet();
      languages.addAll(((Map) classes.get(type)).keySet()); // alphabetize
      ArrayList locales = new ArrayList();
      for(Iterator it = languages.iterator(); it.hasNext(); ) {
         locales.add(new Locale(it.next().toString()));
      }
      return(Locale[]) locales.toArray(new Locale[0]);
   }

   /**
    * Returns true if a String is a valid 2-letter language code.
    *
    * @param lang the 2-letter code
    * @return true if valid 2-letter language code
    */
   protected boolean isLanguage(String lang) {
      String[] languages = Locale.getISOLanguages(); // all standard language codes
      for(int i = 0; i<languages.length; i++) {
         if(languages[i].equals(lang)) {
            return true;
         }
      }
      return false;
   }

   // ______________________________ private methods _____________________________
   private void addDropDownItem(LocaleItem item) {
      // sort items for dropdown: default first, then alphabetized
      Map items = new TreeMap();
      LocaleItem defaultItem = item.isDefault() ? item : null;
      if(!item.isDefault()) {
         items.put(item.language.toLowerCase(), item);
      }
      for(int i = 0; i<localeDropDown.getItemCount(); i++) {
         LocaleItem next = (LocaleItem) localeDropDown.getItemAt(i);
         if(next.isDefault()) {
            defaultItem = next;
         } else {
            items.put(next.language.toLowerCase(), next);
         }
      }
      localeDropDown.removeAllItems();
      if(defaultItem!=null) {
         localeDropDown.addItem(defaultItem);
      }
      for(Iterator it = items.keySet().iterator(); it.hasNext(); ) {
         localeDropDown.addItem(items.get(it.next()));
      }
   }

   /**
    *              Gets the localized value of a property.
    *
    *              @param type the class requesting the localized value
    *              @param key the string to localize
    *              @param defaultValue the default if no localized value found
    *              @param locale the locale
    *              @return the localized string
    */
   private synchronized String getProperty(Class type, String key, String defaultValue, Locale locale) {
      if(defaultValue==null) {
         defaultValue = key;
      }
      if(type==null) {
         return defaultValue;
      }
      if(!getDefaults(type).keySet().contains(key)) {
         addProperty(type, key, defaultValue);
      }
      Map properties = getProperties(type, locale);
      String value = (String) properties.get(key);
      return value;
   }

   /**
    * Gets the properties file path for a class.
    *
    * @param type the class
    * @return the path
    */
   private String getPath(Class type) {
      if(type==null) {
         return null;
      }
      String path = type.getName();
      // replace all "." with "/"
      int i = path.indexOf("."); //$NON-NLS-1$
      while(i!=-1) {
         path = path.substring(0, i)+"/"+path.substring(i+1); //$NON-NLS-1$
         i = path.indexOf(".");                               //$NON-NLS-1$
      }
      return path;
   }

   /**
    * Gets the default properties for the specified class.
    *
    * @param type the class type
    * @return the default property-value map
    */
   private Map getDefaults(Class type) {
      Map defaults = (Map) defaultProps.get(type);
      if(defaults==null) {
         defaults = new TreeMap();
         synchronized(defaultProps) {
            defaultProps.put(type, defaults);
         }
      }
      return defaults;
   }

   /**
    * Gets the properties for the specified class and locale.
    *
    * @param type the class
    * @param locale the locale
    * @return the properties map
    */
   private Map getProperties(Class type, Locale locale) {
      // look for properties map by class and language
      Map locales = (Map) classes.get(type);
      if(locales==null) {
         locales = new HashMap();
         synchronized(classes) {
            classes.put(type, locales);
         }
      }
      Map properties = (Map) locales.get(locale.getLanguage());
      if(properties==null) {
         properties = new TreeMap();
         locales.put(locale.getLanguage(), properties);
         // fill properties from resource, if any
         String path = getPath(type);
         Resource res = null;
         String lang = locale.getLanguage();
         if(!lang.equals("")) {                                            //$NON-NLS-1$
            res = ResourceLoader.getResource(path+"_"+lang+".properties"); //$NON-NLS-1$ //$NON-NLS-2$
         }
         if(res==null) {
            res = ResourceLoader.getResource(path+".properties");          //$NON-NLS-1$
         }
         if(res!=null) {
            OSPLog.finer(res.getAbsolutePath());
            readProperties(res.openReader(), properties);
         } else {
            // load properties with default values
            Map defaults = getDefaults(type);
            Iterator it = defaults.keySet().iterator();
            while(it.hasNext()) {
               Object key = it.next();
               Object val = defaults.get(key);
               properties.put(key, val);
            }
            // add properties to changed set
            flagChange(properties);
         }
      }
      return properties;
   }

   /**
    * Reads the properties into the specified map.
    *
    * @param input the input reader
    * @param map the map
    */
   private void readProperties(BufferedReader input, Map map) {
      try {
         // read properties line by line and put entries into the map
         String next = input.readLine();
         while(next!=null) {
            int i = next.indexOf("="); //$NON-NLS-1$
            if(i>-1) {
               String key = next.substring(0, i);
               String val = next.substring(i+1);
               map.put(key, val);
            }
            next = input.readLine();
         }
      } catch(IOException ex) {
         return;
      }
   }

   /**
    * Saves the current properties to the specified file.
    *
    * @param fileName the file name
    * @return the name of the saved file, or null if not saved
    */
   private String save(String fileName) {
      if((fileName==null)||fileName.equals("")) { //$NON-NLS-1$
         return null;
      }
      int n = fileName.lastIndexOf("/"); //$NON-NLS-1$
      if(n<0) {
         n = fileName.lastIndexOf("\\"); //$NON-NLS-1$
      }
      if(n>0) {
         String dir = fileName.substring(0, n+1);
         File file = new File(dir);
         if(!file.exists()) {
            XML.createFolders(dir);
         }
         if(!file.exists()) {
            return null;
         }
      }
      // assemble the properties
      StringBuffer content = new StringBuffer();
      String s = XML.stripExtension(fileName);
      content.append("# This is the "+s+".properties file"+XML.NEW_LINE+XML.NEW_LINE); //$NON-NLS-1$ //$NON-NLS-2$
      Iterator it = control.getPropertyNames().iterator();
      while(it.hasNext()) {
         String key = (String) it.next();
         String alias = control.getString(key);
         content.append(key+"="+alias+XML.NEW_LINE); //$NON-NLS-1$
      }
      File file = new File(fileName);
      try {
         if(file.exists()&&!file.canWrite()) {
            JOptionPane.showMessageDialog(null, ToolsRes.getString("Tool.Dialog.ReadOnlyFile")); //$NON-NLS-1$
            return null;
         }
         FileOutputStream stream = new FileOutputStream(file);
         java.nio.charset.Charset charset = java.nio.charset.Charset.forName("UTF-8");           //$NON-NLS-1$
         Writer out = new OutputStreamWriter(stream, charset);
         out = new BufferedWriter(out);
         out.write(content.toString());
         out.flush();
         out.close();
         if(file.exists()) {
            OSPLog.finest(file.getAbsolutePath());
            synchronized(changed) {
               changed.remove(getProperties(classType, locale));
            }
            // disable save button
            saveButton.setEnabled(false);
            return file.getAbsolutePath();
         }
      } catch(IOException ex) {
         OSPLog.warning(ex.getMessage());
      }
      return null;
   }

   /**
    * Creates the GUI.
    */
   private void createGUI() {
      XMLTableModel model = new XMLTableModel(control) {

         public String getColumnName(int column) {
            return(column==0) ? ToolsRes.getString("TranslatorTool.ColumnTitle.Property") : //$NON-NLS-1$
               ToolsRes.getString("TranslatorTool.ColumnTitle.PropValue");                  //$NON-NLS-1$
         }
      };
      // listen to ToolsRes for locale changes
      ToolsRes.addPropertyChangeListener("locale", new PropertyChangeListener() { //$NON-NLS-1$

         public void propertyChange(PropertyChangeEvent e) {
            Locale locale = (Locale) e.getNewValue();
            if(locale!=null) {
               setLocale(locale);
            }
         }
      });
      // configure the frame
      contentPane.setPreferredSize(dim);
      setContentPane(contentPane);
      setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);
      // create the XMLTable
      table = new XMLTable(model);
      table.addMouseListener(new MouseAdapter() {

         public void mousePressed(MouseEvent e) {
            if(e.isPopupTrigger()||(e.getButton()==MouseEvent.BUTTON3)
                ||(e.isControlDown()&&(System.getProperty("os.name").indexOf("Mac")>-1))) {                          //$NON-NLS-1$ //$NON-NLS-2$
               // find clicked row
               for(int i = 0; i<table.getRowCount(); i++) {
                  Rectangle rect = table.getCellRect(i, 0, true);
                  if(rect.contains(e.getX(), e.getY())) {
                     table.setRowSelectionInterval(i, i);
                     final String name = (String) table.getValueAt(i, 0);
                     // show popup menu
                     JPopupMenu popup = new JPopupMenu();
                     JMenuItem removeItem = new JMenuItem(ToolsRes.getString("TranslatorTool.Popup.MenuItem.Remove") //$NON-NLS-1$
                                                          +" \""+name+"\"");                                         //$NON-NLS-1$ //$NON-NLS-2$
                     popup.add(removeItem);
                     removeItem.addActionListener(new ActionListener() {

                        public void actionPerformed(ActionEvent ae) {
                           removeProperty(classType, name);
                        }
                     });
                     popup.show(table, e.getX(), e.getY()+8);
                  }
               }
            }
         }
      });
      table.addPropertyChangeListener(new PropertyChangeListener() {

         public void propertyChange(PropertyChangeEvent e) {
            // update current properties map
            Object val = e.getNewValue();
            if(val instanceof TableModelEvent) {
               TableModelEvent event = (TableModelEvent) val;
               int row = event.getFirstRow();
               if(row<0) {
                  return;
               }
               Map properties = getProperties(classType, locale);
               String key = (String) table.getValueAt(row, 0);
               String alias = (String) table.getValueAt(row, 1);
               if((alias!=null)&&!alias.equals("")) { //$NON-NLS-1$
                  properties.put(key, alias);
               } else {
                  table.setValueAt(key, row, 1);
                  properties.put(key, key);
               }
               // refresh objects associated with current class
               refreshAssociates(classType);
               // add properties to changed set
               flagChange(properties);
               // enable save button
               saveButton.setEnabled(true);
            }
         }
      });
      JToolBar toolbar = new JToolBar();
      toolbar.setFloatable(false);
      contentPane.add(toolbar, BorderLayout.NORTH);
      JScrollPane tableScroller = new JScrollPane(table);
      contentPane.add(tableScroller, BorderLayout.CENTER);
      JToolBar buttonbar = new JToolBar();
      buttonbar.setFloatable(false);
      contentPane.add(buttonbar, BorderLayout.SOUTH);
      descriptionLabel = new JLabel();
      descriptionLabel.setBorder(BorderFactory.createEmptyBorder(0, 3, 0, 6));
      toolbar.add(descriptionLabel);
      // assemble locale dropdown
      localeDropDown = new JComboBox();
      LocaleItem selectedItem = new LocaleItem(locale);
      localeDropDown.addItem(selectedItem);
      Locale[] locales = OSPRuntime.getInstalledLocales();
      for(int i = 0; i<locales.length; i++) {
         if(locales[i].getDisplayLanguage().equals(locale.getDisplayLanguage())) {
            continue;
         }
         addDropDownItem(new LocaleItem(locales[i]));
      }
      localeDropDown.setSelectedItem(selectedItem);
      localeDropDown.setEditable(true);
      localeDropDown.addActionListener(new ActionListener() {

         public void actionPerformed(ActionEvent e) {
            Object next = localeDropDown.getSelectedItem();
            if(next==null) {
               return;
            }
            if(next instanceof LocaleItem) {
               ToolsRes.setLocale(((LocaleItem) next).loc);
            } else if(isLanguage(next.toString())) {
               String lang = new Locale(next.toString()).getLanguage();
               // select existing LocaleItem, if any
               Locale locale = null;
               LocaleItem item = null;
               for(int i = 0; i<localeDropDown.getItemCount(); i++) {
                  item = (LocaleItem) localeDropDown.getItemAt(i);
                  if(lang.equals(item.loc.getLanguage())) {
                     locale = item.loc;
                     break;
                  }
                  item = null;
               }
               if(locale==null) {
                  locale = new Locale(next.toString());
                  Map properties = getProperties(classType, locale);
                  flagChange(properties);
               }
               ToolsRes.setLocale(locale);
               if(item!=null) {
                  localeDropDown.setSelectedItem(item);
               }
               localeDropDown.getEditor().selectAll();
            } else {
               localeDropDown.setSelectedIndex(0);
               localeDropDown.getEditor().selectAll();
            }
         }
      });
      toolbar.add(localeDropDown);
      // create buttons
      helpButton = new JButton();
      helpButton.addActionListener(new ActionListener() {

         public void actionPerformed(ActionEvent e) {
            TextFrame frame;
            // show helpURL if available
            if(ResourceLoader.getResource(helpURL)!=null) {
               frame = new TextFrame(helpURL);
            } else {
               String htmlFile = "/org/opensourcephysics/resources/tools/html/translator_tool_help.html"; //$NON-NLS-1$
               frame = new TextFrame(htmlFile);
            }
            frame.setSize(800, 600);
            frame.setVisible(true);
         }
      });
      buttonbar.add(helpButton);
      String imageFile = "/org/opensourcephysics/resources/tools/images/save.gif"; //$NON-NLS-1$
      saveIcon = ResourceLoader.getIcon(imageFile);
      saveButton = new JButton(saveIcon);
      saveButton.addActionListener(new ActionListener() {

         public void actionPerformed(ActionEvent e) {
            save(getPath(classType)+fileExtension);
         }
      });
      saveButton.setEnabled(false);
      buttonbar.add(Box.createHorizontalGlue());
      buttonbar.add(saveButton);
      closeButton = new JButton();
      closeButton.addActionListener(new ActionListener() {

         public void actionPerformed(ActionEvent e) {
            setVisible(false);
         }
      });
      buttonbar.add(closeButton);
      pack();
      // center this on the screen
      Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
      int x = (dim.width-getBounds().width)/2;
      int y = (dim.height-getBounds().height)/2;
      setLocation(x, y);
   }

   /**
    * Refreshes the GUI.
    */
   protected void refreshGUI() {
      String title = ToolsRes.getString("TranslatorTool.Title"); //$NON-NLS-1$
      String fileName = XML.getName(getPath(classType));
      title += " "+fileName; //$NON-NLS-1$
      setTitle(title);
      fileName += fileExtension;
      if(classType!=null) {
         Map properties = getProperties(classType, locale);
         saveButton.setEnabled(changed.contains(properties));
      }
      helpButton.setText(ToolsRes.getString("Tool.Button.Help"));                                   //$NON-NLS-1$
      helpButton.setToolTipText(ToolsRes.getString("Tool.Button.Help.ToolTip"));                    //$NON-NLS-1$
      saveButton.setText(ToolsRes.getString("TranslatorTool.Button.Save"));                         //$NON-NLS-1$
      saveButton.setToolTipText(ToolsRes.getString("TranslatorTool.Button.Save.ToolTip")+fileName); //$NON-NLS-1$
      closeButton.setText(ToolsRes.getString("Tool.Button.Close"));                                 //$NON-NLS-1$
      closeButton.setToolTipText(ToolsRes.getString("Tool.Button.Close.ToolTip"));                  //$NON-NLS-1$
      descriptionLabel.setText(ToolsRes.getString("TranslatorTool.Label.Description"));             //$NON-NLS-1$
      table.refresh();
   }

   /**
    * Refreshes objects associated with the specified class.
    */
   protected void refreshAssociates(Class type) {
      Iterator it = getAssociates(type).iterator();
      while(it.hasNext()) {
         Object obj = it.next();
         if(obj instanceof XMLTable) {
            ((XMLTable) obj).refresh();
         } else if(obj instanceof PropertyChangeListener) {
            ((PropertyChangeListener) obj).propertyChange(
            			new java.beans.PropertyChangeEvent(TOOL, 
            						"translation", null, null)); //$NON-NLS-1$
         }
      }
   }

   private synchronized void flagChange(Map properties) {
      synchronized(changed) {
         changed.add(properties);
      }
   }

   /**
    *              A class to display languages in a JComboBox.
    */
   private class LocaleItem {

      Locale loc;
      String language;
      LocaleItem(Locale locale) {
         loc = locale;
         language = loc.getDisplayLanguage(loc);
         if(isDefault()) {
            language += " ("+ToolsRes.getString("TranslatorTool.Language.Default")+")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
         }
      }

      public String toString() {
         return language;
      }

      public boolean isDefault() {
         return loc.getDisplayLanguage(loc).equals(Locale.getDefault().getDisplayLanguage(loc));
      }
   }
}
/*
 * Open Source Physics software is free software; you can redistribute
 * it and/or modify it under the terms of the GNU General Public License (GPL) as
 * published by the Free Software Foundation; either version 2 of the License,
 * or(at your option) any later version.
 *
 * Code that uses any portion of the code in the org.opensourcephysics package
 * or any subpackage (subdirectory) of this package must must also be be released
 * under the GNU GPL license.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA
 * or view the license online at http://www.gnu.org/copyleft/gpl.html
 *
 * Copyright (c) 2007  The Open Source Physics project
 *                     http://www.opensourcephysics.org
 */
